/*
* Copyright 2011 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/
package org.etherframework.wire.metadata
{
	import flash.utils.Dictionary;
	import flash.utils.getDefinitionByName;
	import flash.utils.getQualifiedClassName;
	
	import org.etherframework.core.utils.ClassType;
	import org.etherframework.core.utils.ClassUtils;
	import org.etherframework.core.reflection.Accessor;
	import org.etherframework.core.reflection.Metadata;
	import org.etherframework.core.reflection.MetadataInfo;
	import org.etherframework.core.reflection.Metatag;
	import org.etherframework.core.reflection.Method;
	import org.etherframework.core.reflection.Variable;
	import org.etherframework.core.reflection.MetadataProperty;
	import org.etherframework.core.reflection.Parameter;

	public final class MetadataParser
	{
		private var _tags:Vector.<String>;
		private const describeClassType:Function = ClassUtils.describeClassType;
		
		public function MetadataParser(tags:Vector.<String> = null)
		{
			_tags = tags;
		}
		
		// TODO: Refactor
		public function getMetadataInfo(value:Object, includeMetatags:Boolean = true):MetadataInfo
		{
			const dictionary:Dictionary = new Dictionary();
			const type:ClassType = describeClassType(value);
			var metatags:Vector.<Metatag>;
			var params:Vector.<Vector.<*>>;
			var name:String;
			var member:XML;
			var memberName:String;
			var isStatic:Boolean;
			
			if (_tags && includeMetatags)
			{
				const metadataTags:XMLList = type.descriptor..metadata;
				
				for each (var item:XML in metadataTags)
				{
					name = item.@name;
					
					if (_tags.indexOf(name) < 0)
						continue;
					
					member = item.parent();
					memberName = member.name();
					isStatic = member.parent().childIndex() < 0;
					params = Vector.<Vector.<*>>(dictionary[name] || 
						(dictionary[name] = new Vector.<Vector.<*>>(4, true)));
					
					switch (memberName)
					{
						case "accessor":
						{
							if (!params[0]) params[0] =  new Vector.<Accessor>();
							params[0].push(new Accessor(member.@name, getType(member.@type), 
								isStatic, getMetadata(item), new QName(member.@uri, member.@name), 
								member.@access));
							break;
						}
						case "method":
						{
							if (!params[1]) params[1] =  new Vector.<Method>();
							params[1].push(new Method(member.@name, getType(member.@returnType), 
								isStatic, getMetadata(item), new QName(member.@uri, member.@name), 
								getParameters(member)));
							break;
						}
						case "variable":
						{
							if (!params[2]) params[2] =  new Vector.<Variable>();
							params[2].push(new Variable(member.@name, getType(member.@type), 
								isStatic, getMetadata(item), new QName(member.@uri, member.@name)));
							break;
						}
						case "factory":
						{
							if (!params[3]) params[3] =  new Vector.<Metadata>();
							params[3].push(getMetadata(item));
							break;
						}
					}
				}
			}
			
			for (name in dictionary)
			{
				params = Vector.<Vector.<*>>(dictionary[name]);
				if (params[0]) params[0].fixed = true;
				if (params[1]) params[1].fixed = true;
				if (params[2]) params[2].fixed = true;
				if (params[3]) params[3].fixed = true;
				
				if (!metatags)
					metatags = new Vector.<Metatag>();
				
				metatags.push(new Metatag(name, params[3] as Vector.<Metadata>, 
					params[2] as Vector.<Variable>, params[0] as Vector.<Accessor>, 
					params[1] as Vector.<Method>));
			}
			
			if (metatags)
				metatags.fixed = true;
			
			return new MetadataInfo(type.descriptor, type.name, metatags, 
				type.reference, getParameters(type.descriptor.factory.constructor[0]));
		}
		
		public function getClassInfo(xml:XML):Object
		{
			return null;
		}
		
		private function getMetadata(metadata:XML):Metadata
		{
			var args:XMLList = metadata.arg;
			var properties:Vector.<MetadataProperty>;
			
			for each (var property:XML in args) 
			{
				if (!properties)
					properties = new Vector.<MetadataProperty>();
				
				properties.push(new MetadataProperty(property.@key, property.@value));
			}
			
			if (properties)
				properties.fixed = true;
			
			return new Metadata(metadata.@name, properties);
		}
		
		private function getParameters(member:XML):Vector.<Parameter>
		{
			if (!member)
				return null;
			
			var result:Vector.<Parameter>;
			var items:XMLList = member.parameter;
			
			for each(var item:XML in items) 
			{
				if (!result)
					result = new Vector.<Parameter>();
				
				result.push(new Parameter(item.@index, getType(item.@type), 
					(item.@optional == "true")));
			}
			
			if (result)
				result.fixed = true;
			
			return result;
		}
		
		private function getType(type:String):Object
		{
			if (!type)
				return type;
			
			switch (type)
			{
				case "void":
				case "*":
				{
					return type;
				}
				default:
				{
					return getDefinitionByName(type);
				}
			}
		}
	}
}